Skip to content

cli: scaffold cobra-based devcontainer CLI#79

Merged
bilby91 merged 4 commits into
mainfrom
cli/initial-scaffold
May 22, 2026
Merged

cli: scaffold cobra-based devcontainer CLI#79
bilby91 merged 4 commits into
mainfrom
cli/initial-scaffold

Conversation

@bilby91
Copy link
Copy Markdown
Member

@bilby91 bilby91 commented May 22, 2026

Summary

Adds cmd/devcontainer, a thin Cobra-based CLI on top of the engine. The CLI is for humans at a terminal — logs to stderr, results to stdout, non-zero exit on failure. Programmatic consumers import the library directly, so there's no outcome-envelope JSON shim (the whole point of this project being a Go library, not another shellout target).

Commands wired in this PR:

Command Engine API
up Engine.Up + streamed event progress
exec Attach + Exec with raw-mode TTY and SIGWINCH forwarding
down / stop Engine.Down (with / without Remove)
read-configuration Resolve → JSON to stdout
run-user-commands Iterates Engine.RunLifecycle across phases

Global flags: --workspace-folder, --config, --runtime=docker|applecontainer (default docker), --log-level. Subcommand flags mirror the upstream @devcontainers/cli names where they map cleanly (--remove-existing-container, --run-initialize-command, --run-secrets-command, --remove-volumes).

Smoke-tested locally against a minimal image-only devcontainer: full lifecycle (upexecread-configurationrun-user-commandsstopdown) green, exit codes propagate (e.g. in-container exit 7 → CLI exit 7).

Follow-ups (already filed)

Why draft

Holding for review on:

  • CLI naming / binary name (currently devcontainer — collides with upstream on PATH; happy to rename if that's a problem)
  • Whether --log-level should be dropped from this PR until cli: plumb --log-level into event filter (or drop the flag) #74 lands, rather than shipping a no-op flag
  • Whether to drop the few upstream-style flag names (--remove-existing-container) in favor of library-style names (--recreate) now, rather than aliasing later

Test plan

  • go build ./cmd/devcontainer
  • devcontainer up against a fixture workspace
  • devcontainer exec -- sh -c 'echo hi' returns 0
  • devcontainer exec -- sh -c 'exit 7' exits 7
  • devcontainer read-configuration | jq . parses
  • devcontainer run-user-commands runs all phases
  • devcontainer stop leaves container stopped; subsequent up restarts it
  • devcontainer down removes the container

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Devcontainer CLI: up, down, stop, exec, run-user-commands, read-configuration.
    • Global flags: --workspace-folder, --config, --runtime, --log-level.
    • down supports --remove-volumes; stop leaves containers intact.
    • exec supports per-command --user and --working-dir, optional TTY allocation.
    • up supports recreate and optional init/secrets steps.
    • run-user-commands executes lifecycle phases and reports completion.
    • Read-configuration prints resolved config as JSON; runtime selection honors platform limits.

Review Change Stack

Add cmd/devcontainer, a thin CLI on top of the engine. The CLI is
meant for humans at a terminal — logs to stderr, results to stdout,
non-zero exit on failure. Tools wanting programmatic access import
the library directly, so no outcome-envelope JSON shim.

Commands wired:
  up                    Engine.Up + streamed event progress
  exec                  Attach + Exec with raw-mode TTY + SIGWINCH
  down / stop           Engine.Down (with/without remove)
  read-configuration    Resolve, JSON to stdout
  run-user-commands     Iterate Engine.RunLifecycle across phases

Global flags: --workspace-folder, --config, --runtime (docker |
applecontainer, default docker), --log-level. Subcommand-specific
flags mirror the upstream @devcontainers/cli names where they map
cleanly (--remove-existing-container, --run-initialize-command,
--run-secrets-command, --remove-volumes).

Follow-ups tracked in #74-#78 (log-level plumbing, exec --env,
--recreate alias, ResolvedConfig json tags, Engine.Build).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 22, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: e32ccbcb-9013-4363-a14b-bd7aabaeabdd

📥 Commits

Reviewing files that changed from the base of the PR and between 91ca6f9 and eea425d.

📒 Files selected for processing (6)
  • cmd/devcontainer/down.go
  • cmd/devcontainer/events.go
  • cmd/devcontainer/exec.go
  • cmd/devcontainer/main.go
  • cmd/devcontainer/run_user_commands.go
  • cmd/devcontainer/up.go
💤 Files with no reviewable changes (1)
  • cmd/devcontainer/exec.go

📝 Walkthrough

Walkthrough

A new CLI package adds a devcontainer command with six subcommands: up (start container), down (remove), stop (stop), exec (run command with optional TTY), read-configuration (show config), and run-user-commands (run lifecycle hooks). The implementation includes signal handling, runtime/engine creation, event rendering, and TTY raw-mode support (no SIGWINCH forwarding).

Changes

DevContainer CLI Implementation

Layer / File(s) Summary
CLI Foundation & Infrastructure
cmd/devcontainer/main.go, cmd/devcontainer/root.go, cmd/devcontainer/events.go, cmd/devcontainer/runtime_applecontainer_*.go, go.mod
Program entrypoint wires SIGINT/SIGTERM cancellation and error handling. Root command defines persistent flags (--workspace-folder, --config, --runtime, --log-level), registers subcommands, and provides helpers to resolve workspace paths, construct runtime backends (docker or applecontainer), and create devcontainer engines. Event printer utilities render engine events to stderr. go.mod updates add cobra, term, and related deps.
Up Command
cmd/devcontainer/up.go
up resolves workspace/config, creates an engine, starts event printing, invokes eng.Up with options for recreation and host-side initialize/secrets commands, stops the event printer, and outputs workspace/container IDs to stderr.
Down & Stop Commands
cmd/devcontainer/down.go
down and stop share execution: resolve workspace/config, create/attach engine, start event printing, call eng.Down with removal/volume-removal options, stop event printer, and print success status (removed or stopped).
Exec Command with TTY Support
cmd/devcontainer/exec.go
exec resolves workspace/config, attaches engine, conditionally enables raw-mode TTY (no SIGWINCH forwarding implemented), forwards stdio and exec options to eng.Exec, and wraps non-zero exits in silentExitError. setupTty manages terminal state restore.
Utility Commands
cmd/devcontainer/read_configuration.go, cmd/devcontainer/run_user_commands.go
read-configuration resolves config and prints it as indented JSON. run-user-commands attaches to a running container, reloads the resolved config, validates/chooses lifecycle phase(s), runs the phases, and prints completion.

Sequence Diagram(s)

(omitted)

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

"I hopped through flags and spawned a shell,
Up woke the box, Down rang the bell,
Exec danced raw with a quiet cheer,
Configs read clear, lifecycle commands near,
A little rabbit bows — the CLI is here!"

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 29.41% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title 'cli: scaffold cobra-based devcontainer CLI' accurately and concisely describes the primary change—adding a new Cobra-based CLI in cmd/devcontainer with multiple subcommands (up, exec, down, stop, read-configuration, run-user-commands).
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch cli/initial-scaffold

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@cmd/devcontainer/exec.go`:
- Around line 63-73: The struct literal for execOpts uses fields that don’t
exist on devcontainer.ExecOptions and the code refers to a non-existent
runtime.TtySize; update cmd/devcontainer/exec.go to match the real API by
removing InitialTtySize and ResizeCh from the devcontainer.ExecOptions literal
(leave Cmd/Env/User/WorkingDir/Tty/Stdin/Stdout/Stderr only) and remove any uses
of runtime.TtySize/resize-channel plumbing in setupTty and the surrounding logic
so the implementation relies only on the Tty boolean and standard streams;
ensure execOpts is passed to eng.Exec and setupTty only toggles terminal mode
when Tty is true to restore compilation against the actual devcontainer/runtime
ExecOptions API.

In `@cmd/devcontainer/root.go`:
- Line 38: The CLI exposes a no-op flag created with pf.StringVar(&f.logLevel,
"log-level", "info", ...) but f.logLevel is never applied; either
remove/deprecate the flag or wire it into your logger setup: read f.logLevel in
the command initialization (where you call your logger bootstrap, e.g.,
setupLogger/initLogger/CreateLogger) and map allowed values
("info"|"debug"|"trace") to the logger's level API, validating unknown values
and returning a user-facing error; if you choose deprecation, mark the flag
hidden or remove the pf.StringVar call and document the removal.
- Around line 80-85: The applecontainer branch returns rt (created by
applecontainer.New) as runtime.Runtime but the stub applecontainer.Runtime on
non-darwin builds lacks BuildImage causing compile errors; fix by wrapping or
adapting the returned rt to satisfy runtime.Runtime: after rt, check/assert
whether rt implements the BuildImage method (or the full runtime.Runtime
interface) and if not return a clear error, or create a small adapter type that
embeds applecontainer.Runtime and implements BuildImage (returning an
appropriate unsupported error) so the returned value always implements
runtime.Runtime; reference applecontainer.New, applecontainer.Options{},
applecontainer.Runtime, and the BuildImage method when locating where to add the
check/adapter.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: a7b94564-80fc-4ea4-bd86-4b1693d8d8aa

📥 Commits

Reviewing files that changed from the base of the PR and between 3385089 and fc3ffec.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (9)
  • cmd/devcontainer/down.go
  • cmd/devcontainer/events.go
  • cmd/devcontainer/exec.go
  • cmd/devcontainer/main.go
  • cmd/devcontainer/read_configuration.go
  • cmd/devcontainer/root.go
  • cmd/devcontainer/run_user_commands.go
  • cmd/devcontainer/up.go
  • go.mod

Comment thread cmd/devcontainer/exec.go Outdated
Comment thread cmd/devcontainer/root.go
Comment thread cmd/devcontainer/root.go Outdated
CI was red because the first commit reached for runtime API surface
that lives in an in-progress local branch, not main:

- runtime.TtySize / ExecOptions.InitialTtySize / ExecOptions.ResizeCh
  aren't on main yet, so exec.go failed to compile in CI. Strip the
  SIGWINCH forwarding for now; TTY raw mode still works. Re-add window
  resize once the runtime change lands.
- runtime/applecontainer's non-darwin stub doesn't implement
  runtime.Runtime, so importing it unconditionally broke the linux
  build. Move applecontainer wiring into a build-tagged file
  (darwin/arm64 only); other platforms return a clear error from
  --runtime=applecontainer.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
bilby91 and others added 2 commits May 22, 2026 17:49
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
golangci-lint surfaced two real issues:

- 18 unchecked fmt.Fprintf returns to stderr (errcheck). Centralise
  the swallow in two tiny helpers (stderrf / outf) so the silenced
  error is named once, not scattered around. Writing to stderr has
  no actionable recovery; the explicit ignore is more honest than a
  blanket exclusion in .golangci.yml.
- exitCodeFor was unused (main.go uses errors.As directly). Removed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@bilby91 bilby91 marked this pull request as ready for review May 22, 2026 21:46
@bilby91 bilby91 merged commit 47b6958 into main May 22, 2026
17 of 18 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant